package cn.itxc.demo.datasource;

import cn.itxc.demo.SpringContextUtil;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 动态数据源注册
 */
@Configuration
public class DynamicDataSourceRegister
        implements ImportBeanDefinitionRegistrar, EnvironmentAware{


    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

//    @Autowired
//    private CuratorFramework curatorFramework;

    private static ApplicationContext context;

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

    private ConversionService conversionService = new DefaultConversionService();
    private PropertyValues dataSourcePropertyValues;

    private static final String PATH = "/zookeeper/test";

    // 如配置文件中未指定数据源类型，使用该默认值
    private static final Object DATASOURCE_TYPE_DEFAULT = "com.mchange.v2.c3p0.ComboPooledDataSource";


    // 数据源
    private DataSource defaultDataSource;
    private Map<String, DataSource> customDataSources = new HashMap<>();

    private static Environment environment;
    private static AnnotationMetadata localImportingClassMetadata ;
    private static BeanDefinitionRegistry localBeanDefinitionRegistry;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        Map<Object, Object> targetDataSources = new HashMap<>();
        // 将主数据源添加到更多数据源中
        targetDataSources.put("dataSource", defaultDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        // 添加更多数据源
        targetDataSources.putAll(customDataSources);
        DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());

        // 创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);

        registry.registerBeanDefinition("dataSource", beanDefinition);

        localBeanDefinitionRegistry = registry;
        localImportingClassMetadata = importingClassMetadata;

        logger.info("Dynamic DataSource Registry");
    }


    @SuppressWarnings("unchecked")
    private DataSource buildDataSource(Map<String, Object> dsMap) {
        try {
            Object type = dsMap.get("type");
            if (type == null)
                type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource

            Class<? extends DataSource> dataSourceType;
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);

            String driverClassName = dsMap.get("driver-class-name").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();

            DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                    .username(username).password(password).type(dataSourceType);
            return factory.build();
//            DruidDataSource dataSource = new DruidDataSource();
//            dataSource.setInitialSize(0);
//            dataSource.setUsername(username);
//            dataSource.setPassword(password);
//            dataSource.setDriverClassName(driverClassName);
//            dataSource.setUrl(url);

//            return dataSource;

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加载多数据源配置
     */
    @Override
    public void setEnvironment(Environment env) {
        environment = env;
        initDefaultDataSource(env);
        initCustomDataSources(env);
    }

    /**
     * 初始化主数据源
     */
    private void initDefaultDataSource(Environment env) {
        // 读取主数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("type", propertyResolver.getProperty("type"));
        dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
        dsMap.put("url", propertyResolver.getProperty("url"));
        dsMap.put("username", propertyResolver.getProperty("username"));
        dsMap.put("password", propertyResolver.getProperty("password"));

        defaultDataSource = buildDataSource(dsMap);

        dataBinder(defaultDataSource, env);
    }

    /**
     * 为DataSource绑定更多数据
     */
    private void dataBinder(DataSource dataSource, Environment env) {
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
        dataBinder.setConversionService(conversionService);
        dataBinder.setIgnoreNestedProperties(false);//false
        dataBinder.setIgnoreInvalidFields(false);//false
        dataBinder.setIgnoreUnknownFields(true);//true
        if (dataSourcePropertyValues == null) {
            Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
            Map<String, Object> values = new HashMap<>(rpr);
            // 排除已经设置的属性
            values.remove("type");
            values.remove("driver-class-name");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }

    /**
     * 初始化更多数据源
     */
    private void initCustomDataSources(Environment env) {
        // 读取配置文件获取更多数据源，也可以通过defaultDataSource读取数据库获取更多数据源
//        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
//        String dsPrefixs = propertyResolver.getProperty("names");

//        spring.datasource.driver-class-name=com.mysql.jdbc.Driver
//        spring.datasource.url=jdbc:mysql://localhost:3306/testDataSource1
//        spring.datasource.username=root
//        spring.datasource.password=123456

        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.90.242:2181", retryPolicy);
        client.start();

        try {
            List<String> AllConfigs = client.getChildren().forPath(PATH);
            for (String config : AllConfigs) {

                byte[] configData = client.getData().forPath(PATH + "/" + config);
                String configDataValue = new String(configData, "utf-8");
                logger.info("Load config key:" + config + " value:" + configDataValue);

                String [] configDataValueArray = configDataValue.split("!");

                Map<String, Object> testMap = new HashMap<>();
                testMap.put("driver-class-name", configDataValueArray[0]);
                testMap.put("url", "jdbc:mysql://" +configDataValueArray[1] + "?useSSL=false");
                testMap.put("username", configDataValueArray[2]);
                testMap.put("password", configDataValueArray[3]);
                DataSource ds = buildDataSource(testMap);
                customDataSources.put(config, ds);
                dataBinder(ds, env);

            }
            addListen(client,PATH);
        } catch (Exception e) {
            logger.error("Load zk config data error", e);
        }

    }


    private void addListen(CuratorFramework client,String path){
        PathChildrenCache childrenCache = new PathChildrenCache(client, path, true);

        PathChildrenCacheListener childrenCacheListener = (client1, event) -> {
            ChildData data = event.getData();

            if (data == null){
                return;
            }

            String path1 = data.getPath();
            String node = getNodeName(path1);
            String evData = new String(data.getData(), "utf-8");
            switch (event.getType()) {
                case CHILD_ADDED:
                    logger.info("Zk child added:" + path1 +  ",key:" + node  + " data :" + evData);
                    //节点增加。
                    zkAddOrUpdate(evData,node);
                    break;
                case CHILD_REMOVED:
                    logger.info("Zk child removed: " + path1 + ",key:" + node );
                    break;
                case CHILD_UPDATED:

                    logger.info("Zk child updated: " + path1 + ",key:" + node + "  data:" + evData);
                    zkAddOrUpdate(evData,node);

                    break;
                default:
                    break;
            }
        };

        childrenCache.getListenable().addListener(childrenCacheListener);
        logger.info("Register zk watcher successfully!");

        try {
            childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
        } catch (Exception e) {

        }
    }

    /**
     * 从node中取出key
     * @param path path
     * @return node的名字
     */
    private String getNodeName(String path) {
        String[] nodes = path.split("/");
        return nodes[3];
    }


    private void zkAddOrUpdate(String ev, String node){
        String configDataValueArray[] = ev.split("!");
        Map<String, Object> testMap = new HashMap<>();
        testMap.put("driver-class-name", configDataValueArray[0]);
        testMap.put("url", "jdbc:mysql://" + configDataValueArray[1] + "?useSSL=false&useUnicode=true&characterEncoding=utf8");
        testMap.put("username", configDataValueArray[2]);
        testMap.put("password", configDataValueArray[3]);
        DataSource ds = buildDataSource(testMap);
        customDataSources.put(node, ds);
        dataBinder(ds, environment);

        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // 将主数据源添加到更多数据源中
        targetDataSources.put("dataSource", defaultDataSource);
        // 添加更多数据源
        targetDataSources.putAll(customDataSources);

        DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringContextUtil.getBean("dataSource", DynamicDataSource.class);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.afterPropertiesSet();
    }





}